import type {
  TypeDefinitionService,
  TypeDefinition,
  TypeCacheStats,
  TypeHttpClient,
  TypeFetchOptions,
  TypeFetchResponse,
  TypeDefinitionMetadata
} from '../interfaces'

/**
 * HTTP client for fetching type definitions
 */
export class TypeHttpClientImpl implements TypeHttpClient {
  private defaultHeaders: Record<string, string> = {
    'Accept': 'application/typescript, text/plain, */*',
    'User-Agent': 'ESM-Import-Resolver/1.0.0'
  }
  private timeout = 10000 // 10 seconds

  async fetch(url: string, options: TypeFetchOptions = {}): Promise<TypeFetchResponse> {
    const controller = new AbortController()
    const timeoutId = setTimeout(() => controller.abort(), options.timeout || this.timeout)

    try {
      const headers = { ...this.defaultHeaders, ...options.headers }

      const response = await fetch(url, {
        headers,
        signal: controller.signal,
        redirect: options.followRedirects !== false ? 'follow' : 'manual'
      })

      const body = await response.text()
      const responseHeaders: Record<string, string> = {}

      response.headers.forEach((value, key) => {
        responseHeaders[key.toLowerCase()] = value
      })

      return {
        status: response.status,
        headers: responseHeaders,
        body,
        url: response.url,
        ok: response.ok,
        timestamp: Date.now()
      }
    } catch (error) {
      if (error instanceof Error && error.name === 'AbortError') {
        throw new Error(`Request timeout after ${options.timeout || this.timeout}ms`)
      }
      throw error
    } finally {
      clearTimeout(timeoutId)
    }
  }

  async head(url: string): Promise<boolean> {
    try {
      const controller = new AbortController()
      const timeoutId = setTimeout(() => controller.abort(), this.timeout)

      const response = await fetch(url, {
        method: 'HEAD',
        headers: this.defaultHeaders,
        signal: controller.signal
      })

      clearTimeout(timeoutId)
      return response.ok
    } catch {
      return false
    }
  }

  setDefaultHeaders(headers: Record<string, string>): void {
    this.defaultHeaders = { ...this.defaultHeaders, ...headers }
  }

  setTimeout(timeout: number): void {
    this.timeout = timeout
  }
}

/**
 * Type definition service implementation
 */
export class TypeDefinitionManager implements TypeDefinitionService {
  private cache = new Map<string, TypeDefinition>()
  private metadata = new Map<string, TypeDefinitionMetadata>()
  private httpClient: TypeHttpClient
  private stats: TypeCacheStats = {
    totalEntries: 0,
    hits: 0,
    misses: 0,
    hitRate: 0,
    totalSize: 0,
    lastCleanup: Date.now(),
    entriesByStatus: {
      valid: 0,
      expired: 0,
      failed: 0
    }
  }
  private cacheTTL = 24 * 60 * 60 * 1000 // 24 hours
  private maxRetries = 3
  private retryDelay = 1000 // 1 second

  constructor(httpClient?: TypeHttpClient) {
    this.httpClient = httpClient || new TypeHttpClientImpl()
  }

  /**
   * Fetch type definitions for a package URL
   */
  async fetchTypes(packageUrl: string): Promise<TypeDefinition | null> {
    // Early validation: skip empty URLs or local file paths
    if (!packageUrl || packageUrl.trim() === '') {
      console.log(`⚠️ Skipping empty package URL`)
      return null
    }

    // Skip local file paths (relative imports)
    if (this.isLocalFilePath(packageUrl)) {
      console.log(`⚠️ Skipping local file path: ${packageUrl}`)
      return null
    }

    // Check cache first
    const cached = this.getCachedTypes(packageUrl)
    if (cached) {
      this.stats.hits++
      this.updateHitRate()
      return cached
    }

    this.stats.misses++
    this.updateHitRate()

    try {
      console.log(`🔍 Attempting to fetch types for: ${packageUrl}`)

      // Try to fetch real types from esm.sh with retry logic
      const typeDefinition = await this.fetchTypesWithRetry(packageUrl, 1)

      if (typeDefinition) {
        this.cacheTypes(packageUrl, typeDefinition)
        console.log(`✅ Successfully fetched types for ${typeDefinition.packageName}`)
        return typeDefinition
      } else {
        // Fallback to mock types for demo purposes
        console.log(`⚠️ Failed to fetch real types, falling back to mock for: ${packageUrl}`)
        const mockTypeDefinition = await this.createMockTypeDefinition(packageUrl)
        if (mockTypeDefinition) {
          this.cacheTypes(packageUrl, mockTypeDefinition)
          console.log(`✅ Successfully created mock types for ${mockTypeDefinition.packageName}`)
          return mockTypeDefinition
        }
      }
    } catch (error) {
      console.warn(`Failed to fetch types for ${packageUrl}:`, error)

      // Try mock types as final fallback
      try {
        const mockTypeDefinition = await this.createMockTypeDefinition(packageUrl)
        if (mockTypeDefinition) {
          this.cacheTypes(packageUrl, mockTypeDefinition)
          console.log(`✅ Using mock types as fallback for ${mockTypeDefinition.packageName}`)
          return mockTypeDefinition
        }
      } catch (mockError) {
        console.warn(`Failed to create mock types for ${packageUrl}:`, mockError)
      }
    }

    return null
  }

  /**
   * Create mock type definition for demo purposes
   */
  private async createMockTypeDefinition(packageUrl: string): Promise<TypeDefinition | null> {
    const packageName = this.extractPackageName(packageUrl)

    // Create mock type definitions for common packages
    const mockTypes = this.getMockTypeContent(packageName)
    if (!mockTypes) {
      return null
    }

    return {
      url: `${packageUrl}/index.d.ts`,
      content: mockTypes,
      headers: { 'content-type': 'application/typescript' },
      timestamp: Date.now(),
      packageName
    }
  }

  /**
   * Get mock type content for common packages
   */
  private getMockTypeContent(packageName: string): string | null {
    const mockTypes: Record<string, string> = {
      'react': `// React type definitions
export interface Component<P = {}, S = {}> {}

export function useState<T>(initialState: T | (() => T)): [T, (value: T | ((prev: T) => T)) => void];
export function useEffect(effect: () => void | (() => void), deps?: any[]): void;
export function useCallback<T extends (...args: any[]) => any>(callback: T, deps: any[]): T;
export function useMemo<T>(factory: () => T, deps: any[]): T;

export namespace React {
  interface FC<P = {}> {
    (props: P): JSX.Element | null;
  }
}

declare const React: {
  FC: typeof React.FC;
  useState: typeof useState;
  useEffect: typeof useEffect;
  useCallback: typeof useCallback;
  useMemo: typeof useMemo;
};

export default React;

declare global {
  namespace JSX {
    interface Element {}
    interface IntrinsicElements {
      div: any;
      span: any;
      [elemName: string]: any;
    }
  }
}`,
      'lodash': `// Lodash type definitions
export function debounce<T extends (...args: any[]) => any>(func: T, wait: number): T;
export function throttle<T extends (...args: any[]) => any>(func: T, wait: number): T;
export function cloneDeep<T>(obj: T): T;
export function merge<T>(target: T, ...sources: any[]): T;

declare const _: {
  debounce: typeof debounce;
  throttle: typeof throttle;
  cloneDeep: typeof cloneDeep;
  merge: typeof merge;
};

export default _;`,
      'axios': `// Axios type definitions
export interface AxiosResponse<T = any> {
  data: T;
  status: number;
  statusText: string;
  headers: any;
}

export interface AxiosRequestConfig {
  url?: string;
  method?: string;
  headers?: any;
  data?: any;
}

export function get<T = any>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
export function post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;

declare const axios: {
  get: typeof get;
  post: typeof post;
};

export default axios;`,
      '@babel/core': `// Babel Core type definitions
export interface TransformResult {
  code?: string | null;
  map?: any;
}

export interface TransformOptions {
  presets?: string[];
  plugins?: string[];
}

export function transform(code: string, options?: TransformOptions): Promise<TransformResult | null>;`,
      'fs': `// Node.js fs module type definitions
export function readFile(path: string, encoding: string): Promise<string>;
export function writeFile(path: string, data: string): Promise<void>;
export function existsSync(path: string): boolean;`,
      'path': `// Node.js path module type definitions
export function join(...paths: string[]): string;
export function resolve(...paths: string[]): string;
export function dirname(path: string): string;
export function basename(path: string): string;
export function extname(path: string): string;`,
      'crypto': `// Node.js crypto module type definitions
export interface Hash {
  update(data: string): Hash;
  digest(encoding: string): string;
}

export function createHash(algorithm: string): Hash;`
    }

    return mockTypes[packageName] || null
  }

  /**
   * Get cached type definitions
   */
  getCachedTypes(packageUrl: string): TypeDefinition | null {
    const cached = this.cache.get(packageUrl)
    if (!cached) {
      return null
    }

    const meta = this.metadata.get(packageUrl)
    if (meta && this.isExpired(meta)) {
      this.cache.delete(packageUrl)
      this.metadata.delete(packageUrl)
      this.stats.entriesByStatus.expired++
      return null
    }

    // Update access metadata
    if (meta) {
      meta.lastAccessed = Date.now()
      meta.accessCount++
    }

    return cached
  }

  /**
   * Cache type definitions
   */
  cacheTypes(packageUrl: string, types: TypeDefinition): void {
    this.cache.set(packageUrl, types)

    const metadata: TypeDefinitionMetadata = {
      packageName: this.extractPackageName(packageUrl),
      typeUrl: types.url,
      packageUrl,
      fetchedAt: Date.now(),
      lastAccessed: Date.now(),
      accessCount: 1,
      size: types.content.length,
      hash: this.generateHash(types.content),
      isDefinitelyTyped: types.url.includes('@types/'),
      qualityScore: this.calculateQualityScore(types)
    }

    this.metadata.set(packageUrl, metadata)
    this.updateStats()
  }

  /**
   * Clear all cached types
   */
  clearCache(): void {
    this.cache.clear()
    this.metadata.clear()
    this.resetStats()
  }

  /**
   * Clear expired cache entries
   */
  clearExpiredCache(): void {
    const now = Date.now()
    let expiredCount = 0

    for (const [url, meta] of this.metadata.entries()) {
      if (this.isExpired(meta)) {
        this.cache.delete(url)
        this.metadata.delete(url)
        expiredCount++
      }
    }

    this.stats.lastCleanup = now
    this.stats.entriesByStatus.expired += expiredCount
    this.updateStats()
  }

  /**
   * Get cache statistics
   */
  getCacheStats(): TypeCacheStats {
    return { ...this.stats }
  }

  /**
   * Check if types are available for a package
   */
  async hasTypes(packageUrl: string): Promise<boolean> {
    // Check cache first
    if (this.getCachedTypes(packageUrl)) {
      return true
    }

    // Try to resolve type URL
    try {
      const typeUrl = await this.resolveTypeUrl(packageUrl)
      if (typeUrl) {
        return await this.httpClient.head(typeUrl)
      }
    } catch {
      // Ignore errors for availability check
    }

    return false
  }

  /**
   * Prefetch types for multiple packages (simulated for demo)
   */
  async prefetchTypes(packageUrls: string[]): Promise<void> {
    console.log('📝 Simulating type prefetching for demo purposes')
    console.log('📦 Packages that would be fetched:', packageUrls)

    // In a real implementation, this would fetch from esm.sh
    // For demo purposes, we just simulate the process
    const simulatedResults = packageUrls.map(url => ({
      url,
      status: 'simulated',
      message: 'Would fetch type definitions from esm.sh'
    }))

    console.log('✅ Simulated type prefetching completed:', simulatedResults)

    // Simulate async operation
    await new Promise(resolve => setTimeout(resolve, 100))
  }

  /**
   * Fetch types with retry logic
   */
  private async fetchTypesWithRetry(packageUrl: string, attempt: number): Promise<TypeDefinition | null> {
    const maxAttempts = this.maxRetries

    for (let currentAttempt = attempt; currentAttempt <= maxAttempts; currentAttempt++) {
      try {
        console.log(`🔄 Attempt ${currentAttempt}/${maxAttempts} for ${packageUrl}`)

        // First try to resolve the type URL
        const typeUrl = await this.resolveTypeUrl(packageUrl)
        if (!typeUrl) {
          console.log(`❌ Could not resolve type URL for ${packageUrl}`)
          return null
        }

        console.log(`🔍 Fetching types from: ${typeUrl}`)

        // Fetch the type definition
        const response = await this.httpClient.fetch(typeUrl, {
          timeout: 15000, // 15 seconds for type fetching
          noDts: packageUrl.includes('no-dts=true')
        })

        if (!response.ok) {
          throw new Error(`HTTP ${response.status}: ${response.url}`)
        }

        console.log(`✅ Successfully fetched types from ${typeUrl}`)

        return {
          url: typeUrl,
          content: response.body,
          headers: response.headers,
          timestamp: response.timestamp,
          packageName: this.extractPackageName(packageUrl)
        }
      } catch (error) {
        console.log(`❌ Attempt ${currentAttempt} failed for ${packageUrl}:`, error)

        if (currentAttempt === maxAttempts) {
          console.log(`❌ All ${maxAttempts} attempts failed for ${packageUrl}`)
          throw error
        }

        // Wait before retrying with exponential backoff
        const delay = this.retryDelay * Math.pow(2, currentAttempt - 1)
        console.log(`⏳ Waiting ${delay}ms before retry...`)
        await this.delay(delay)
      }
    }

    return null
  }

  /**
   * Resolve type definition URL from package URL
   */
  private async resolveTypeUrl(packageUrl: string): Promise<string | null> {
    try {
      console.log(`🔍 Resolving type URL for: ${packageUrl}`)

      // Early validation: skip empty URLs or local file paths
      if (!packageUrl || packageUrl.trim() === '' || this.isLocalFilePath(packageUrl)) {
        console.log(`⚠️ Skipping invalid or local package URL: ${packageUrl}`)
        return null
      }

      // For esm.sh URLs, try to get types directly by adding ?dts query parameter
      // if (packageUrl.includes('esm.sh')) {
      //   const dtsUrl = `${packageUrl}?dts`
      //   console.log(`📝 Trying esm.sh dts URL: ${dtsUrl}`)

      //   try {
      //     const dtsResponse = await this.httpClient.fetch(dtsUrl, { timeout: 5000 })
      //     if (dtsResponse.ok && dtsResponse.body.includes('declare')) {
      //       console.log(`✅ Found types at esm.sh dts URL: ${dtsUrl}`)
      //       return dtsUrl
      //     }
      //   } catch (dtsError) {
      //     console.log(`❌ esm.sh dts URL failed: ${dtsError}`)
      //   }
      // }

      // Try to get the X-TypeScript-Types header from the package URL
      try {
        const response = await this.httpClient.fetch(packageUrl, {
          timeout: 10000
        })

        if (response.ok && response.headers['x-typescript-types']) {
          const typeUrl = response.headers['x-typescript-types']
          console.log(`📝 Found X-TypeScript-Types header: ${typeUrl}`)

          // Handle relative URLs
          if (typeUrl.startsWith('/')) {
            const baseUrl = new URL(packageUrl).origin
            return `${baseUrl}${typeUrl}`
          }
          if (!typeUrl.startsWith('http')) {
            const baseUrl = packageUrl.substring(0, packageUrl.lastIndexOf('/'))
            return `${baseUrl}/${typeUrl}`
          }
          return typeUrl
        }
      } catch (headerError) {
        console.log(`❌ Failed to check X-TypeScript-Types header: ${headerError}`)
      }

      // Fallback: try common type definition patterns
      console.log(`🔄 Trying common type patterns for: ${packageUrl}`)
      return this.tryCommonTypePatterns(packageUrl)
    } catch (error) {
      console.warn(`❌ Failed to resolve type URL for ${packageUrl}:`, error)
      return null
    }
  }

  /**
   * Try common type definition URL patterns
   */
  private tryCommonTypePatterns(packageUrl: string): string | null {
    const packageName = this.extractPackageName(packageUrl)
    console.log(`🔍 Trying common patterns for package: ${packageName}`)

    // Validate package name is not empty
    if (!packageName || packageName.trim() === '') {
      console.log(`❌ Empty package name, skipping type patterns`)
      return null
    }

    // Try DefinitelyTyped pattern
    if (!packageName.startsWith('@types/')) {
      const typesPackage = packageName.startsWith('@')
        ? `@types/${packageName.substring(1).replace('/', '__')}`
        : `@types/${packageName}`

      const typesUrl = `https://esm.sh/${typesPackage}?dts`
      console.log(`📝 Trying DefinitelyTyped pattern: ${typesUrl}`)
      return typesUrl
    }

    // If it's already a @types package, try to get its .d.ts directly
    if (packageName.startsWith('@types/')) {
      const dtsUrl = `${packageUrl}?dts`
      console.log(`📝 Trying @types package dts: ${dtsUrl}`)
      return dtsUrl
    }

    console.log(`❌ No common pattern found for: ${packageName}`)
    return null
  }

  /**
   * Check if a URL represents a local file path
   */
  private isLocalFilePath(packageUrl: string): boolean {
    // Check for relative paths
    if (packageUrl.startsWith('./') || packageUrl.startsWith('../')) {
      return true
    }

    // Check for absolute file paths
    if (packageUrl.startsWith('/') || packageUrl.startsWith('file://')) {
      return true
    }

    // Check for local file extensions
    const localFileExtensions = ['.ts', '.tsx', '.js', '.jsx', '.json', '.css', '.scss', '.less']
    if (localFileExtensions.some(ext => packageUrl.endsWith(ext))) {
      return true
    }

    // Check if it's not a valid HTTP URL
    try {
      const url = new URL(packageUrl)
      // If it's not http/https, consider it local
      return !['http:', 'https:'].includes(url.protocol)
    } catch {
      // If URL parsing fails and it doesn't look like a package name, consider it local
      return packageUrl.includes('/') || packageUrl.includes('\\')
    }
  }

  /**
   * Extract package name from URL
   */
  private extractPackageName(packageUrl: string): string {
    try {
      const url = new URL(packageUrl)
      const pathname = url.pathname

      // Remove leading slash and extract package name
      const parts = pathname.substring(1).split('/')

      if (parts[0].startsWith('@')) {
        // Scoped package
        return `${parts[0]}/${parts[1]}`
      }

      return parts[0]
    } catch {
      return packageUrl
    }
  }

  /**
   * Check if metadata is expired
   */
  private isExpired(metadata: TypeDefinitionMetadata): boolean {
    return Date.now() - metadata.fetchedAt > this.cacheTTL
  }

  /**
   * Generate hash for content validation
   */
  private generateHash(content: string): string {
    let hash = 0
    for (let i = 0; i < content.length; i++) {
      const char = content.charCodeAt(i)
      hash = ((hash << 5) - hash) + char
      hash = hash & hash // Convert to 32-bit integer
    }
    return hash.toString(36)
  }

  /**
   * Calculate quality score for type definitions
   */
  private calculateQualityScore(types: TypeDefinition): number {
    let score = 0.5 // Base score

    // Bonus for DefinitelyTyped packages
    if (types.url.includes('@types/')) {
      score += 0.3
    }

    // Bonus for larger type definitions (more comprehensive)
    if (types.content.length > 10000) {
      score += 0.1
    }

    // Bonus for recent timestamps
    const age = Date.now() - types.timestamp
    if (age < 7 * 24 * 60 * 60 * 1000) { // Less than a week old
      score += 0.1
    }

    return Math.min(score, 1.0)
  }

  /**
   * Update cache statistics
   */
  private updateStats(): void {
    this.stats.totalEntries = this.cache.size
    this.stats.totalSize = Array.from(this.metadata.values())
      .reduce((total, meta) => total + meta.size, 0)

    this.stats.entriesByStatus.valid = this.cache.size
    this.updateHitRate()
  }

  /**
   * Update hit rate calculation
   */
  private updateHitRate(): void {
    const total = this.stats.hits + this.stats.misses
    this.stats.hitRate = total > 0 ? this.stats.hits / total : 0
  }

  /**
   * Reset statistics
   */
  private resetStats(): void {
    this.stats = {
      totalEntries: 0,
      hits: 0,
      misses: 0,
      hitRate: 0,
      totalSize: 0,
      lastCleanup: Date.now(),
      entriesByStatus: {
        valid: 0,
        expired: 0,
        failed: 0
      }
    }
  }

  /**
   * Delay utility for retry logic
   */
  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms))
  }
}